#include "vpentrance.h"
#include <QDebug>
#include <future>
extern "C"
{
#include "libavutil/time.h"
#include "SDL.h"
}
VpEntrance* VpEntrance::s_INSTANCE           = nullptr;
int         VpEntrance::s_RUNNING_THREAD_NUM = 0;
VpEntrance::VpEntrance(QObject *parent)
    : QObject{parent}
{
    m_video_stream_idx     = -1;
    m_audio_stream_idx     = -1;
    m_sws_serial           = 0;
    m_swr_serial           = 0;
    m_serial               = 0;
    m_audio_clock          = 0;
    m_video_clock          = 0;
    m_audio_buffer_size    = 0;
    m_audio_buffer_size1   = 0;
    m_audio_callback_time  = 0;
    m_audio_volume         = 64;
    m_stop                 = false;
    m_pause                = false;
    m_seek_req             = false;
    m_fmt_ctx              = nullptr;
    m_video_codec_ctx      = nullptr;
    m_audio_codec_ctx      = nullptr;
    m_audio_buffer         = nullptr;
    m_audio_buffer1        = nullptr;
}

bool VpEntrance::start(string file_name, int width, int height)
{
    m_stop   = false;
    m_audio_fme_list.start();
    m_video_fme_list.start();
    m_audio_pkt_list.start();
    m_video_pkt_list.start();
    bool res = start_initial(file_name, width >> 4 << 4, height >> 4 << 4);
    if(res)
        start_play();
    else {
        qDebug()<<"initialize unsuccessfully";
    }
    return res;
}

VpEntrance *VpEntrance::getInstance()
{
    if (!s_INSTANCE) {
        s_INSTANCE = new VpEntrance();
    }
    return s_INSTANCE;
}

bool VpEntrance::start_send_Image(VpFrameQueue *video_fme_queue, struct VIDEO_PARAMS *video_params)
{
    bool res = true;
    class thread send_image_th(work_send_image, this, video_fme_queue, video_params);
    send_image_th.detach();
    return res;
}

void VpEntrance::work_send_image(VpEntrance* vp_entrance, VpFrameQueue *video_fme_queue, struct VIDEO_PARAMS *video_params)
{
    qDebug()<<"send image thread start";
    s_RUNNING_THREAD_NUM                += 1;
    int    imagesize                     = 0;
    int    ret                           = -1;
    double time                          = 0;
    uint8_t* buffer = nullptr;
    while(!vp_entrance->m_stop)
    {
        if(video_params->serial != vp_entrance->m_video_params_chg.serial) {
            video_params->serial  = vp_entrance->m_video_params_chg.serial;
            video_params->width   = vp_entrance->m_video_params_chg.width;
            video_params->height  = vp_entrance->m_video_params_chg.height;
            sws_freeContext(video_params->sws_ctx);
            video_params->sws_ctx = nullptr;
            video_params->sws_ctx = sws_getContext(vp_entrance->m_video_codec_ctx->width, vp_entrance->m_video_codec_ctx->height, vp_entrance->m_video_codec_ctx->pix_fmt
                                                   , video_params->width, video_params->height, video_params->format, SWS_BILINEAR, nullptr, nullptr, nullptr);
        }
        {
            unique_lock<mutex> lock(vp_entrance->m_mutex);
            while(vp_entrance->m_pause && !vp_entrance->m_stop)
                vp_entrance->m_pause_cond.wait_for(lock, chrono::milliseconds(30));
        }
        VpFrame srcframe;
        VpFrame dstframe;
        double  last_duration = 0;
        double  delay         = 0;
        do {
            srcframe = video_fme_queue->pop();
            video_fme_queue->m_cond.notify_one();
        }   while(srcframe.serial() != vp_entrance->m_video_fme_list.serial());
        if(vp_entrance->m_last_video_frame.serial() != srcframe.serial()) {
            vp_entrance->m_last_video_frame = srcframe;
        }
        if(vp_entrance->m_last_video_frame.m_frame->pts != AV_NOPTS_VALUE && srcframe.m_frame->pts != AV_NOPTS_VALUE) {
            last_duration = srcframe.m_pts - vp_entrance->m_last_video_frame.m_pts;
            delay = vp_entrance->compute_target_delay(last_duration);
        } else {
            delay = 1.0 / av_q2d(vp_entrance->m_video_codec_ctx->framerate);
        }
        qDebug()<<"delay---------"<<delay;
        av_usleep((int64_t)(delay * 1000000.0));
        dstframe.m_frame->width  = video_params->width;
        dstframe.m_frame->height = video_params->height;
        dstframe.m_frame->format = video_params->format;
        if(av_frame_get_buffer(dstframe.m_frame, 1) < 0)
            return;
        imagesize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, dstframe.m_frame->width, dstframe.m_frame->height, 1);
        dstframe.m_pts = srcframe.m_pts;
        ret = sws_scale(video_params->sws_ctx, srcframe.m_frame->data, srcframe.m_frame->linesize, 0, srcframe.m_frame->height, dstframe.m_frame->data, dstframe.m_frame->linesize);
        av_frame_copy_props(dstframe.m_frame, srcframe.m_frame);
        if(ret > 0)
        {
            buffer = (uint8_t*)av_malloc(imagesize);
            memcpy(buffer, dstframe.m_frame->data[0], imagesize);
            time = av_gettime_relative() / 1000000.0;
            emit vp_entrance->sendImage(buffer, dstframe.m_frame->width, dstframe.m_frame->height);
            vp_entrance->m_last_video_frame = dstframe;
            vp_entrance->m_vidclk.set_clock_at(srcframe.m_pts, dstframe.serial(), time);
            qDebug()<<"video clock:"<<vp_entrance->m_vidclk.get_clock()<<"|||"<<"audio clcok:"<<vp_entrance->m_audclk.get_clock()<<"diff"<<vp_entrance->m_vidclk.get_clock() - vp_entrance->m_audclk.get_clock();
            int seconds = static_cast<int>(round(vp_entrance->m_audclk.get_clock()));
            emit vp_entrance->sendSeconds(seconds);
        }
    }
    {
        lock_guard<mutex> lock(vp_entrance->m_mutex);
        s_RUNNING_THREAD_NUM -= 1;
    }
    emit vp_entrance->sendSeconds(0);
    qDebug()<<"send image thread end";
    return;
}

bool VpEntrance::start_initial(string file_name, int width, int height)
{
    bool res = true;
    if(file_name.empty())
        res = false;
    else
    {
        future<bool> result = async(launch::async, initial, this, file_name, width, height);
        while(result.wait_for(chrono::milliseconds(50)) != future_status::ready) {}
        res = result.get();
    }
    if(res)
    {
        m_video_pkt_list.setStream_idx(m_video_stream_idx);
        m_video_fme_list.setStream_idx(m_video_stream_idx);
        m_audio_pkt_list.setStream_idx(m_audio_stream_idx);
        m_audio_fme_list.setStream_idx(m_audio_stream_idx);
        int all_seconds = m_fmt_ctx->duration / AV_TIME_BASE;
        emit sendAllSeconds(all_seconds);
    }
    return res;
}

void VpEntrance::release_resource()
{
    m_video_fme_list.flush_queue();
    m_audio_fme_list.flush_queue();
    m_video_pkt_list.flush_queue();
    m_audio_pkt_list.flush_queue();
    if(m_fmt_ctx) {
        avformat_close_input(&m_fmt_ctx);
        avformat_free_context(m_fmt_ctx);
    }
    if(m_audio_codec_ctx)
        avcodec_free_context(&m_audio_codec_ctx);
    if(m_video_codec_ctx)
        avcodec_free_context(&m_video_codec_ctx);
    if(m_video_params.sws_ctx) {
        sws_freeContext(m_video_params.sws_ctx);
        m_video_params.sws_ctx = nullptr;
    }

    if(m_audio_params.swr_ctx)
        swr_free(&m_audio_params.swr_ctx);
}

void VpEntrance::stop()
{
    m_video_stream_idx              = -1;
    m_audio_stream_idx              = -1;
    m_sws_serial                    = 0;
    m_swr_serial                    = 0;
    m_audio_clock                   = 0;
    m_video_clock                   = 0;
    m_audio_buffer_size             = 0;
    m_audio_buffer_size1            = 0;
    m_audio_callback_time           = 0;
    m_stop   = true;
    m_video_fme_list.stop();
    m_audio_fme_list.stop();
    m_video_pkt_list.stop();
    m_audio_pkt_list.stop();
    m_decode_audio_th.stop();
    m_decode_video_th.stop();
    m_demux_th.stop();
    SDL_CloseAudioDevice(m_audioDevice_id);
    SDL_Quit();
    {
        unique_lock<mutex> lock(m_mutex);
        while(s_RUNNING_THREAD_NUM != 0)
            m_wait_for_stop_cond.wait_for(lock, chrono::milliseconds(30));
    }
    release_resource();
    m_state                         = STOP;
    m_serial                        = 0;
    m_last_video_frame.m_frame->pts = AV_NOPTS_VALUE;
}

void VpEntrance::start_play()
{
    m_demux_th.start(m_fmt_ctx, m_audio_stream_idx, m_video_stream_idx, &m_audio_pkt_list, &m_video_pkt_list, &m_serial);
    m_decode_video_th.start(m_video_codec_ctx, &m_video_pkt_list, &m_video_fme_list);
    m_decode_audio_th.start(m_audio_codec_ctx, &m_audio_pkt_list, &m_audio_fme_list);
    start_send_Image(&m_video_fme_list, &m_video_params);
    m_state = PLAYING;
}

void VpEntrance::alter_speed(double speed)
{
    m_vidclk.set_clock_speed(speed);
    m_audclk.set_clock_speed(speed);
    m_audio_params_chg.freq  = m_audio_params_backup.freq / speed;
    m_audio_params_chg.serial++;
}

void VpEntrance::pause_or_resume()
{
    if(m_pause) {
        m_pause = false;
        m_state = PLAYING;
        m_pause_cond.notify_all();
    }
    else {
        m_pause = true;
        m_state = PAUSE;
    }
}

void VpEntrance::alter_video_size(int width, int height)
{
    m_video_params_chg.serial++;
    m_video_params_chg.width  = width >> 4 << 4;
    m_video_params_chg.height = height >> 4 << 4;
}

int VpEntrance::input_audio_buffer(VpFrame src)
{
    if(src.m_frame->pts == AV_NOPTS_VALUE)
        return -1;
    if(m_audio_params.serial != m_audio_params_chg.serial) {
        m_audio_params      = m_audio_params_chg;
        m_audio_params_src  = m_audio_params_chg;
    }
    int resample_size = -1;
    int data_size = av_samples_get_buffer_size(nullptr, src.m_frame->ch_layout.nb_channels, src.m_frame->nb_samples, (AVSampleFormat)src.m_frame->format, 0);
    if (data_size < 0) {
        qDebug()<<"av_samples_get_buffer_size() failed";
        return -1;
    }
    if(src.m_frame->format         != m_audio_params_src.format                               ||
        src.m_frame->sample_rate   != m_audio_params_src.freq                                 ||
        (m_wanted_nb_samples       != src.m_frame->nb_samples && !m_audio_params_src.swr_ctx) ||
        av_channel_layout_compare(&src.m_frame->ch_layout, &m_audio_params_src.ch_layout))
    {
        swr_free(&m_audio_params.swr_ctx);
        swr_alloc_set_opts2(&m_audio_params.swr_ctx, &m_audio_params.ch_layout, m_audio_params.format, m_audio_params.freq
                            , &src.m_frame->ch_layout, (AVSampleFormat)src.m_frame->format, src.m_frame->sample_rate, 0, nullptr);
        if(!m_audio_params.swr_ctx || swr_init(m_audio_params.swr_ctx)) {
            qDebug()<<"cannot create swrctx";
            return -1;
        }
        if(av_channel_layout_copy(&m_audio_params_src.ch_layout, &src.m_frame->ch_layout) < 0)
            return -1;
        m_audio_params_src.format  = (AVSampleFormat)src.m_frame->format;
        m_audio_params_src.freq    = src.m_frame->sample_rate;
        m_audio_params_src.swr_ctx = m_audio_params.swr_ctx;
    }

    if(m_audio_params.swr_ctx) {
        int len2 = 0;
        uint8_t **in  = src.m_frame->extended_data;
        uint8_t **out = &m_audio_buffer1;
        int out_count = (int64_t)m_wanted_nb_samples * m_audio_params.freq / src.m_frame->sample_rate + 256;
        if (m_wanted_nb_samples != src.m_frame->nb_samples) {
            if (swr_set_compensation(m_audio_params.swr_ctx, (m_wanted_nb_samples - src.m_frame->nb_samples) * m_audio_params.freq / src.m_frame->sample_rate,
                                     m_wanted_nb_samples * m_audio_params.freq / src.m_frame->sample_rate) < 0) {
                qDebug()<<"swr_set_compensation() failed";
                return -1;
            }
        }
        av_fast_malloc(&m_audio_buffer1, &m_audio_buffer_size1, data_size);
        if(!m_audio_buffer1) {
            qDebug()<<"No Memory";
            return -1;
        }
        len2 = swr_convert(m_audio_params.swr_ctx, out, out_count, (const uint8_t**)in, src.m_frame->nb_samples);
        if(len2 < 0) {
            qDebug()<<"swr_convert() failed";
            return -1;
        }
        if(len2 == out_count) {
            qDebug()<<"audio buffer is probably too small";
            if (swr_init(m_audio_params.swr_ctx) < 0)
                swr_free(&m_audio_params.swr_ctx);
        }
        m_audio_buffer = m_audio_buffer1;
        resample_size = len2 * m_audio_params.ch_layout.nb_channels * av_get_bytes_per_sample(m_audio_params.format);
    } else {
        memcpy(m_audio_buffer, src.m_frame->data[0], data_size);
        resample_size = data_size;
    }
    if(src.m_frame->pts != AV_NOPTS_VALUE)
        m_audio_clock = src.m_pts + (double)src.m_frame->nb_samples / src.m_frame->sample_rate;
    else
        m_audio_clock = NAN;
    return resample_size;
}

double VpEntrance::compute_target_delay(double duration)
{
    double sync_threshold = FFMIN(AV_SYNC_THRESHOLD_MIN, duration);
    double diff           = 0;
    double delay          = duration;
    diff                  = m_vidclk.get_clock() - m_audclk.get_clock();
    if(!isnan(diff) && diff < MAX_FRAME_DURATION) {
        if(diff <= -sync_threshold) {
            delay = FFMAX(0, duration + diff);
        } else if(diff >= sync_threshold) {
            delay += diff;
        }
    } else {
        delay = MAX_FRAME_DURATION;
    }
    return delay;
}

bool VpEntrance::initial(VpEntrance *vp_entrance, string file_name, int width, int height)
{
    SDL_AudioSpec wanted_spec, spec;
    vp_entrance->m_video_params.width  = width;
    vp_entrance->m_video_params.height = height;
    vp_entrance->m_video_params.format = AV_PIX_FMT_RGB24;
    vp_entrance->m_video_params.serial = 0;
    if(vp_entrance->m_state == PLAYING) {
        unique_lock<mutex> lock(vp_entrance->m_mutex);
        while(vp_entrance->m_state != STOP)
            vp_entrance->m_cond.wait_for(lock, chrono::milliseconds(50));
    }
    if (SDL_Init(SDL_INIT_AUDIO) < 0) {
        printf("SDL initial failed: %s\n", SDL_GetError());
    }
    wanted_spec.channels          = 2;
    wanted_spec.freq              = 44100;
    wanted_spec.format            = AUDIO_S16SYS;
    wanted_spec.silence           = 0;
    wanted_spec.samples           = 512;
    wanted_spec.callback          = audio_sdl_callback;
    wanted_spec.userdata          = vp_entrance;
    SDL_AudioDeviceID audioDevice = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, 0);
    vp_entrance->m_audioDevice_id              = audioDevice;
    if (audioDevice == 0) {
        qDebug()<< "open audio device failed: " << SDL_GetError() ;
        SDL_Quit();
        return false;
    }
    SDL_PauseAudioDevice(audioDevice, 0);
    vp_entrance->m_audio_params.format        = AV_SAMPLE_FMT_S16;
    vp_entrance->m_audio_params.freq          = spec.freq;
    av_channel_layout_uninit(&vp_entrance->m_audio_params.ch_layout);
    av_channel_layout_default(&vp_entrance->m_audio_params.ch_layout, spec.channels);
    vp_entrance->m_audio_params.frame_size    = av_samples_get_buffer_size(NULL, vp_entrance->m_audio_params.ch_layout.nb_channels, 1, vp_entrance->m_audio_params.format, 1);
    vp_entrance->m_audio_params.bytes_per_sec = av_samples_get_buffer_size(NULL, vp_entrance->m_audio_params.ch_layout.nb_channels, vp_entrance->m_audio_params.freq, vp_entrance->m_audio_params.format, 1);
    vp_entrance->m_audio_params.swr_ctx       = nullptr;
    vp_entrance->m_audio_params_src           = vp_entrance->m_audio_params;
    vp_entrance->m_audio_params_backup        = vp_entrance->m_audio_params_src;
    vp_entrance->m_audio_params_chg           = vp_entrance->m_audio_params_src;
    if (vp_entrance->m_audio_params.bytes_per_sec <= 0 || vp_entrance->m_audio_params.frame_size <= 0) {
        qDebug()<<"av_samples_get_buffer_size failed";
        return false;
    }
    if (avformat_open_input(&vp_entrance->m_fmt_ctx, file_name.c_str(), nullptr, nullptr) != 0) {
        qDebug() << "无法打开文件: " << file_name ;
            return false;
    }

    if (avformat_find_stream_info(vp_entrance->m_fmt_ctx, nullptr) < 0) {
        qDebug() << "无法获取文件流信息: " << file_name ;
                avformat_close_input(&vp_entrance->m_fmt_ctx);
        return false;
    }

    for (unsigned int i = 0; i < vp_entrance->m_fmt_ctx->nb_streams; i++) {
        AVCodecParameters *codec_params = vp_entrance->m_fmt_ctx->streams[i]->codecpar;
        if (codec_params->codec_type == AVMEDIA_TYPE_VIDEO && vp_entrance->m_video_stream_idx == -1) {
            vp_entrance->m_video_stream_idx = i;
        }
        if (codec_params->codec_type == AVMEDIA_TYPE_AUDIO && vp_entrance->m_audio_stream_idx == -1) {
            vp_entrance->m_audio_stream_idx = i;
        }
        if (vp_entrance->m_video_stream_idx != -1 && vp_entrance->m_audio_stream_idx != -1) {
            break;
        }
    }

    if (vp_entrance->m_video_stream_idx != -1) {
        const AVCodec *vcodec = avcodec_find_decoder(vp_entrance->m_fmt_ctx->streams[vp_entrance->m_video_stream_idx]->codecpar->codec_id);
        if (vcodec) {
            vp_entrance->m_video_codec_ctx = avcodec_alloc_context3(vcodec);
            if(!vp_entrance->m_video_codec_ctx)
                vp_entrance->m_video_stream_idx       = -1;
            if(avcodec_parameters_to_context(vp_entrance->m_video_codec_ctx, vp_entrance->m_fmt_ctx->streams[vp_entrance->m_video_stream_idx]->codecpar) < 0)
                vp_entrance->m_video_stream_idx       = -1;
            if(avcodec_open2(vp_entrance->m_video_codec_ctx, vcodec, nullptr) < 0)
                vp_entrance->m_video_stream_idx       = -1;
            vp_entrance->m_video_fme_list.m_tb        = vp_entrance->m_fmt_ctx->streams[vp_entrance->m_video_stream_idx]->time_base;
            vp_entrance->m_video_pkt_list.m_codec_ctx = vp_entrance->m_video_codec_ctx;
        }
    }

    if (vp_entrance->m_audio_stream_idx != -1) {
        const AVCodec *acodec = avcodec_find_decoder(vp_entrance->m_fmt_ctx->streams[vp_entrance->m_audio_stream_idx]->codecpar->codec_id);
        if (acodec) {
            vp_entrance->m_audio_codec_ctx = avcodec_alloc_context3(acodec);
            if(!vp_entrance->m_audio_codec_ctx)
                vp_entrance->m_audio_stream_idx       = -1;
            if(avcodec_parameters_to_context(vp_entrance->m_audio_codec_ctx, vp_entrance->m_fmt_ctx->streams[vp_entrance->m_audio_stream_idx]->codecpar) < 0)
                vp_entrance->m_audio_stream_idx       = -1;
            if(avcodec_open2(vp_entrance->m_audio_codec_ctx, acodec, nullptr) < 0)
                vp_entrance->m_audio_stream_idx       = -1;
            vp_entrance->m_audio_fme_list.m_tb        = vp_entrance->m_fmt_ctx->streams[vp_entrance->m_audio_stream_idx]->time_base;
            if(vp_entrance->m_audio_codec_ctx)
                vp_entrance->m_wanted_nb_samples      = vp_entrance->m_audio_codec_ctx->frame_size;
            vp_entrance->m_audio_pkt_list.m_codec_ctx = vp_entrance->m_audio_codec_ctx;
        }
    }

    if(vp_entrance->m_video_stream_idx != -1)
        vp_entrance->m_video_params.sws_ctx = sws_getContext(vp_entrance->m_video_codec_ctx->width, vp_entrance->m_video_codec_ctx->height, vp_entrance->m_video_codec_ctx->pix_fmt
                                                             , vp_entrance->m_video_params.width, vp_entrance->m_video_params.height, vp_entrance->m_video_params.format, SWS_BILINEAR, nullptr, nullptr, nullptr);

    vp_entrance->m_video_params_chg = vp_entrance->m_video_params;
    return (vp_entrance->m_video_stream_idx != -1 || vp_entrance->m_audio_stream_idx != -1);
}

void VpEntrance::seek_time(int seek_time)
{
    m_seek_time = seek_time;
    m_serial++;
    m_demux_th.seek_time(m_seek_time);
}

void VpEntrance::set_audio_volume(int volume)
{
    m_audio_volume = volume;
}

void audio_sdl_callback(void *userdata, uint8_t *stream, int len)
{
    int buffer_size                    = 0;
    int len1                           = 0;
    int hw_size                        = len;
    VpEntrance *vp_entrance            = (VpEntrance*)userdata;
    int audio_frame_serial             = vp_entrance->m_audio_fme_list.serial();
    vp_entrance->m_audio_callback_time = av_gettime_relative();
    memset(stream, 0, len);
    while(len > 0)
    {
        {
            unique_lock<mutex> lock(vp_entrance->m_mutex);
            while(vp_entrance->m_pause)
                vp_entrance->m_pause_cond.wait(lock);
        }
        if(vp_entrance->m_audio_buffer_idx >= vp_entrance->m_audio_buffer_size) {
            VpFrame frame = vp_entrance->m_audio_fme_list.pop();
            vp_entrance->m_audio_fme_list.m_cond.notify_one();
            buffer_size   = vp_entrance->input_audio_buffer(frame);
            if(buffer_size < 0) {
                qDebug()<<"input audio buffer error!";
                break;
            } else {
                vp_entrance->m_audio_buffer_size = buffer_size;
            }
            vp_entrance->m_audio_buffer_idx = 0;
        }
        len1 = vp_entrance->m_audio_buffer_size - vp_entrance->m_audio_buffer_idx;
        if(len1 > len)
            len1 = len;
        SDL_MixAudioFormat(stream, vp_entrance->m_audio_buffer+vp_entrance->m_audio_buffer_idx, AUDIO_S16SYS, len1, vp_entrance->m_audio_volume);
        len                             -= len1;
        stream                          += len1;
        vp_entrance->m_audio_buffer_idx += len1;
    }
    vp_entrance->m_audio_can_write_size = vp_entrance->m_audio_buffer_size - vp_entrance->m_audio_buffer_idx;
    if(!isnan(vp_entrance->m_audio_clock)) {
        vp_entrance->m_audclk.set_clock_at(vp_entrance->m_audio_clock - (double)(2 * hw_size + vp_entrance->m_audio_can_write_size) / vp_entrance->m_audio_params.bytes_per_sec
                                           ,  audio_frame_serial, vp_entrance->m_audio_callback_time / 1000000.0);
    }

}
